#include <stdlib.h>
#include <math.h>
#include <ios>
#include <iostream>
#include <fstream>
using namespace std;
#include <glut.h>
#include "sgi.h"
#include "Camera.h"
#include "BMP.h"

#define HEIGHTMAP 1024 * 1024
#define MAP_SIZE 1024
#define STEP_SIZE 16
#define LINE 1
#define TEXTURED 2

unsigned char heightMap[HEIGHTMAP];

int renderType = TEXTURED;

GLuint heightmap_texture;
GLuint sky_texture;

Camera camera = Camera();

GLfloat scaleValue = 0.1f;
float skyMovCounter = 0.0f;

GLuint terrainList;
GLuint skyList;

float lightAmbient[] = { 1.0f, 1.0f, 1.0f, 0.5f };
float lightDiffuse[] = { 1.0f, 1.0f, 1.0f, 0.5f };
float lightPosition[] = { -50.0f, 200.0f, -50.0f, 1.0f };
int currX = 0, currY = 0;
GLUquadric* quadric;

void setVertexColor(unsigned char* pHeightMap, int x, int y);
int height(unsigned char* pHeightMap, int X, int Y);
void loadRawFile(const char* filename, unsigned char* pHeightMap);
void renderHeightMap(unsigned char* pHeightMap);
void loadHeightMapTexture(const char* imageFilename);
void loadSkyTexture(const char* imageFilename);
void setLightning(void);
void enableCullingMode(bool value);
void drawSky();

void myinit(void) {

	glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
	glClearDepth(1.0f); // Depth Buffer Setup
	glEnable(GL_DEPTH_TEST); // Enables Depth Testing
	glDepthFunc(GL_LEQUAL); // The Type Of Depth Testing To Do
	glShadeModel(GL_SMOOTH); // try setting this to GL_FLAT and see what happens
	glEnable(GL_MULTISAMPLE);
	glEnable(GL_NORMALIZE);
	// Really Nice Perspective Calculations
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
	loadRawFile("./Heightfield.raw",
			heightMap);
	loadHeightMapTexture(
			"./Heightfield.bmp");

	setLightning();

	terrainList = glGenLists(1);
	glNewList(terrainList, GL_COMPILE);
	renderHeightMap(heightMap);
	glEndList();

	quadric = gluNewQuadric();
	gluQuadricNormals(quadric, GLU_SMOOTH);
	gluQuadricTexture(quadric, true);

	loadSkyTexture("./sky.bmp");

	skyList = glGenLists(1);
	glNewList(skyList, GL_COMPILE);
	drawSky();
	glEndList();

	camera.moveForward(-100.0f);
	camera.rotateX(10);
}

void loadSkyTexture(const char* imageFilename) {

	BITMAPINFO *BitmapInfo; /* Bitmap information */
	GLubyte *BitmapBits; /* Bitmap data */

	glEnable(GL_TEXTURE_2D);

	glGenTextures(1, &sky_texture);

	BitmapBits = LoadDIBitmap(imageFilename, &BitmapInfo);

	glBindTexture(GL_TEXTURE_2D, sky_texture);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, BitmapInfo->bmiHeader.biWidth,
			BitmapInfo->bmiHeader.biHeight, 0, GL_RGB, GL_UNSIGNED_BYTE,
			BitmapBits);

	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

}

void loadHeightMapTexture(const char* imageFilename) {

	BITMAPINFO *BitmapInfo; /* Bitmap information */
	GLubyte *BitmapBits; /* Bitmap data */

	glEnable(GL_TEXTURE_2D);

	glGenTextures(1, &heightmap_texture);

	BitmapBits = LoadDIBitmap(imageFilename, &BitmapInfo);

	glBindTexture(GL_TEXTURE_2D, heightmap_texture);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, BitmapInfo->bmiHeader.biWidth,
			BitmapInfo->bmiHeader.biHeight, 0, GL_RGB, GL_UNSIGNED_BYTE,
			BitmapBits);
}

void display(void) {
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	enableCullingMode(true);
	glLoadIdentity();

	Vector3 camPosition = camera.getCameraPosition();
	Vector3 camTarget = camera.getCameraTarget();
	Vector3 upVector = camera.getUpVector();

	gluLookAt(camPosition.X,camPosition.Y, camPosition.Z, camTarget.X,
			camTarget.Y, camTarget.Z, upVector.X, upVector.Y, upVector.Z);

	glScalef(scaleValue, scaleValue * 1.0f, scaleValue);

	setLightning();

	glBindTexture(GL_TEXTURE_2D, heightmap_texture);
	glCallList(terrainList);
	glCallList(skyList);
	glutSwapBuffers();
}

void setLightning() {
	glLightfv(GL_LIGHT0, GL_AMBIENT, lightAmbient);
	glLightfv(GL_LIGHT0, GL_DIFFUSE, lightDiffuse);
	glLightfv(GL_LIGHT0, GL_POSITION, lightPosition);
	glEnable(GL_LIGHT0);
	glEnable(GL_LIGHTING);
}

void renderHeightMap(unsigned char* pHeightMap) {

	glEnable(GL_LIGHT0); // enable lightning
	glEnable(GL_LIGHTING);

	if (renderType == LINE)
		glBegin(GL_LINES);
	else
		glBegin(GL_QUADS);

	glBegin(GL_QUADS);

	for (int X = 0; X < (MAP_SIZE - STEP_SIZE); X += STEP_SIZE)
		for (int Y = 0; Y < (MAP_SIZE - STEP_SIZE); Y += STEP_SIZE) {

			int x = X;
			int y = height(pHeightMap, X, Y);
			int z = Y;
			if (renderType == TEXTURED)
				glTexCoord2f((float) x / (float) MAP_SIZE, (float) z
						/ (float) MAP_SIZE);
			else
				setVertexColor(pHeightMap, x, z);
			glVertex3i(x, y, z);
			x = X;
			y = height(pHeightMap, X, Y + STEP_SIZE);
			z = Y + STEP_SIZE;
			if (renderType == TEXTURED)
				glTexCoord2f((float) x / (float) MAP_SIZE, (float) (z + 1)
						/ (float) MAP_SIZE);
			else
				setVertexColor(pHeightMap, x, z);
			glVertex3i(x, y, z);

			x = X + STEP_SIZE;
			y = height(pHeightMap, X + STEP_SIZE, Y + STEP_SIZE);
			z = Y + STEP_SIZE;
			if (renderType == TEXTURED)
				glTexCoord2f((float) (x + 1) / (float) MAP_SIZE,
						(float) (z + 1) / (float) MAP_SIZE);
			else
				setVertexColor(pHeightMap, x, z);
			glVertex3i(x, y, z);

			x = X + STEP_SIZE;
			y = height(pHeightMap, X + STEP_SIZE, Y);
			z = Y;
			if (renderType == TEXTURED)
				glTexCoord2f((float) (x + 1) / (float) MAP_SIZE, (float) z
						/ (float) MAP_SIZE);
			else
				setVertexColor(pHeightMap, x, z);
			glVertex3i(x, y, z);
		}

	glEnd();
	glColor4f(1.0f, 1.0f, 1.0f, 1.0f);

	glDisable(GL_LIGHT0); // enable lightning
	glDisable(GL_LIGHTING);
}

void drawSky() {
	Vector3 camPosition = camera.getCameraPosition();
	glBindTexture(GL_TEXTURE_2D, sky_texture);
	enableCullingMode(false);

	glPushMatrix();
	glRotatef(-91.7f, 1.0f, 0.0f, 0.0f);
	glRotatef(skyMovCounter, 0.0f, 0.0f, 1.0f);
	glTranslatef(camPosition.X + 500, camPosition.Y, camPosition.Z - MAP_SIZE
			* scaleValue * 0.5f);
	double clipPlane1[] = { 0.0f, 0.0f, 1.0f, 0.5f };
	glClipPlane(GL_CLIP_PLANE1, clipPlane1);
	glEnable(GL_CLIP_PLANE1);

	gluSphere(quadric, 5000, 50, 5);
	glDisable(GL_CLIP_PLANE1);
	glPopMatrix();


	glPushMatrix();
	glRotatef(-91.7f, 1.0f, 0.0f, 0.0f);

	glTranslatef(camPosition.X + 500, camPosition.Y, camPosition.Z - MAP_SIZE
			* scaleValue * 0.5f);
	double clipPlane2[] = { 0.0f, 0.0f, -1.0f, 0.5f };
	glClipPlane(GL_CLIP_PLANE2, clipPlane2);
	glEnable(GL_CLIP_PLANE2);
	gluSphere(quadric, 5000, 50, 5);
	glDisable(GL_CLIP_PLANE2);
	glPopMatrix();

}

void setVertexColor(unsigned char* pHeightMap, int x, int y) {
	int h = height(pHeightMap, x, y);

	if (h >= 250)
		glColor3f(0.984313f, 0.3019607f, 0.3019607f);
	else if (h > 200)
		glColor3f(1.0f, 0.5607843f, 0.243147f);
	else if (h > 150)
		glColor3f(0.50196f, 1.0f, 0.50196f);
	else if (h > 100)
		glColor3f(0.011764f, 0.55294117f, 0.1490196f);
	else if (h > 50)
		glColor3f(0.011764f, 0.6862745f, 0.945098f);
	else
		glColor3f(0.011764f, 0.270588f, 0.945098f);

}

int height(unsigned char* pHeightMap, int X, int Y) {
	int x = X % MAP_SIZE;
	int y = Y % MAP_SIZE;

	return pHeightMap[x + (y * MAP_SIZE)] & 0xFF;
}

void enableCullingMode(bool value) {
	if (value) {
		glCullFace(GL_BACK);
		glEnable(GL_CULL_FACE);
		glFrontFace(GL_CCW);
	} else
		glDisable(GL_CULL_FACE);
}

void reshape(int w, int h) {
	h = (h == 0) ? 1 : h;
	glViewport(0, 0, w, h);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(45, (float) w / h, 1, 10000); // near plane 10000
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
}

void loadRawFile(const char* filename, unsigned char* pHeightMap) {
	FILE *file;
	file = fopen(filename, "rb");
	fread(pHeightMap, 1, HEIGHTMAP, file);
	fclose(file);

	for (int i = 0; i < 1024 * 1024; i++) {
		pHeightMap[i] &= 0xFF;
	}

}

void keyboard_func(unsigned char key, int x, int y) {
	switch (key) {
	case 27: // ESCAPE key
		exit(0);
		break;
	case 'w':
		camera.moveForward(-0.50f);
		break;
	case 's':
		camera.moveForward(0.50f);
		break;
	case 'a':
		camera.strafeRight(-1.0f);
		break;
	case 'd':
		camera.strafeRight(1.0f);
		break;
	}
	glutPostRedisplay();
}

void processMouseActiveMotion(int x, int y) {


}

void idle(){
	display();
}

int main(int argc, char** argv) {

	/* Standard GLUT initialization */

	glutInit(&argc, argv);
	//      initialize for double buffering, RGB color, and depth tests
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
	glutInitWindowSize(800, 600);
	glutInitWindowPosition(70, 70);
	glutCreateWindow("Test");
	glutDisplayFunc(display);
	glutReshapeFunc(reshape);
	glutKeyboardFunc(keyboard_func);
	glutMotionFunc(processMouseActiveMotion);
	glutIdleFunc(idle);
	myinit();
	glutMainLoop();

	return 0;
}
